package com.google.code.retrobox.editor.tracker;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.Console;
import java.util.HashMap;
import java.util.Map;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;

import com.google.code.retrobox.editor.tracker.model.Pattern;

public class TrackerEditor extends JPanel implements ActionListener,
		KeyListener, Receiver {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private JFrame parent = null;
	public JTable table = null;

	private JScrollPane tableScrollPane = null;
	private JButton playButton = new JButton("Play");
	private JButton stopButton = new JButton("Stop");
	private JButton nextButton = new JButton("Next");
	private JButton prevButton = new JButton("Prev");

	private JTextArea console = new JTextArea();

	private String[] columnNames = { "Nr ", "Note", "Vol", "Fx" };

	String[][] data = { { "00", "067", "093", "000" },
			{ "01", "000", "000", "000" }, { "02", "000", "000", "000" },
			{ "03", "000", "000", "000" }, { "04", "070", "093", "000" },
			{ "05", "000", "000", "000" }, { "06", "000", "000", "000" },
			{ "07", "067", "093", "000" }, { "08", "999", "000", "000" },
			{ "09", "067", "093", "000" }, { "10", "072", "093", "000" },
			{ "11", "000", "000", "000" }, { "12", "067", "093", "000" },
			{ "13", "000", "000", "000" }, { "14", "065", "093", "000" },
			{ "15", "000", "000", "000" }, { "16", "067", "093", "000" },
			{ "17", "000", "000", "000" }, { "18", "000", "000", "000" },
			{ "19", "000", "000", "000" }, { "20", "074", "093", "000" },
			{ "21", "000", "000", "000" }, { "22", "000", "000", "000" },
			{ "23", "067", "093", "000" }, { "24", "999", "000", "000" },
			{ "25", "067", "093", "000" }, { "26", "075", "093", "000" },
			{ "27", "000", "000", "000" }, { "28", "074", "093", "000" },
			{ "29", "000", "000", "000" }, { "30", "070", "093", "000" },
			{ "31", "000", "000", "000" }, { "32", "067", "093", "000" },
			{ "33", "000", "000", "000" }, { "34", "074", "093", "000" },
			{ "35", "000", "000", "000" }, { "36", "079", "093", "000" },
			{ "37", "000", "000", "000" }, { "38", "067", "093", "000" },
			{ "39", "065", "093", "000" }, { "40", "999", "000", "000" },
			{ "41", "065", "093", "000" }, { "42", "062", "093", "000" },
			{ "43", "000", "000", "000" }, { "44", "070", "093", "000" },
			{ "45", "000", "000", "000" }, { "46", "067", "093", "000" },
			{ "47", "000", "000", "000" }, { "48", "000", "000", "000" },
			{ "49", "000", "000", "000" }, { "50", "000", "000", "000" },
			{ "51", "000", "000", "000" }, { "52", "000", "003", "000" },
			{ "53", "000", "000", "000" }, { "54", "000", "000", "000" },
			{ "55", "000", "000", "000" }, { "56", "000", "000", "000" },
			{ "57", "000", "000", "000" }, { "58", "000", "000", "000" },
			{ "59", "000", "000", "000" }, { "60", "000", "000", "000" },
			{ "61", "000", "000", "000" }, { "62", "999", "000", "000" },
			{ "63", "000", "000", "000" } };
	public Pattern pattern = new Pattern(data);
	
	public int bpm = 120;

	private int sequencerDelay = 125;

	private Timer sequencerTimer;

	SoundSystem soundSystem;

	private Map<String, Integer> noteMap = new HashMap<String, Integer>();

	private int currentOctave = 3;

	public TrackerEditor(JFrame parent) {
		this.parent = parent;

		init();
	}

	private void init() {
		// Setup pattern table and disable table model for editing.
		DefaultTableModel model = new DefaultTableModel(pattern.getData(),
				columnNames);
		table = new JTable(model) {
			/**
			 * 
			 */
			private static final long serialVersionUID = 1L;

			public boolean isCellEditable(int rowIndex, int colIndex) {
				return false; // Disallow the editing of any cell
			}
		};

		table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

		JScrollPane tableScrollPane = new JScrollPane(table);
		table.setFillsViewportHeight(true);
		table.addKeyListener(this);

		BorderLayout borderLayout = new BorderLayout();
		this.setLayout(borderLayout);
		this.add(tableScrollPane, BorderLayout.WEST);

		// Setup control button panel
		playButton.addActionListener(this);
		stopButton.addActionListener(this);
		nextButton.addActionListener(this);
		prevButton.addActionListener(this);
		JPanel controlsButtonBarPanel = new JPanel();
		controlsButtonBarPanel.setLayout(new FlowLayout());
		controlsButtonBarPanel.add(playButton);
		controlsButtonBarPanel.add(stopButton);
		controlsButtonBarPanel.add(nextButton);
		controlsButtonBarPanel.add(prevButton);
		this.add(controlsButtonBarPanel, BorderLayout.CENTER);

		// Setup console
		console = new JTextArea(5, 120);
		console.setEditable(false);
		console.addKeyListener(this);
		JScrollPane consolePane = new JScrollPane(console,
				JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
				JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		this.add(consolePane, BorderLayout.SOUTH);

		// Setup sound system
		soundSystem = new SoundSystem();
		if (soundSystem.init() == false) {
			log("Failed init soundSystem.");
		}

		// Setup note mapping
		initNoteMapping();

	}

	private void initNoteMapping() {
		this.noteMap.put("<", 0);
		this.noteMap.put("q", 1);
		this.noteMap.put("w", 2);
		this.noteMap.put("s", 3);
		this.noteMap.put("x", 4);
		this.noteMap.put("c", 5);
		this.noteMap.put("f", 6);
		this.noteMap.put("v", 7);
		this.noteMap.put("g", 8);
		this.noteMap.put("b", 9);
		this.noteMap.put("h", 10);
		this.noteMap.put("n", 11);
	}

	private int notePlayed(String key, int octave) {
		if (this.noteMap.get(key) != null) {
			return ((int) this.noteMap.get(key)) + 24 + (octave * 12);
		}
		return -1;
	}

	private int counter = 0;

	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == playButton) {

			startPatternPlay();
		} else if (e.getSource() == stopButton) {
			stopPatternPlay();
		} else if (e.getSource() == prevButton) {
		} else if (e.getSource() == nextButton) {
		} else if (e.getSource() == sequencerTimer) {
			// this.console.setText(this.console.getText()
			// + "sequencerTimer invoked.\n");
			counter = (int) soundSystem.getSequencerPosition();
			log(String.valueOf(counter) + ':' + soundSystem.isSequenceRunning());
			if (counter > 64) {
				counter = 0;
			}

			table.getSelectionModel().setSelectionInterval(counter, counter);
			Rectangle cellRect = table.getCellRect(counter, 1, false);
			if (cellRect != null) {
				table.scrollRectToVisible(cellRect);
			}

		} else {
			log(e.getActionCommand());
		}
	}

	@Override
	/* * Handle the key typed event from the text field. */
	public void keyTyped(KeyEvent e) {
		int note = notePlayed(String.valueOf(e.getKeyChar()), currentOctave);
		editNoteInCurrentPattern(note);
	}

	private void editNoteInCurrentPattern(int note) {
		int column = table.getSelectedColumn();
		int row = table.getSelectedRow();
		editNoteInCurrentPattern(note, column, row);
	}

	public void editNoteInCurrentPattern(int note, int column, int row) {
		if (column > -1 && row > -1) {
			if (note > -1) {
				table.setValueAt(String.format("%03d",note), row, Pattern.TRACK_0_NOTE);
				table.setValueAt("093", row, Pattern.TRACK_0_VOLUME);
				pattern.getData()[row][Pattern.TRACK_0_NOTE] = String.valueOf(note);
				pattern.getData()[row][Pattern.TRACK_0_VOLUME] = "093";
				try {
					this.soundSystem.playNote(note);
				} catch (InvalidMidiDataException e1) {
					this.log(e1.getMessage());
				} catch (MidiUnavailableException e1) {
					this.log(e1.getMessage());
				}
			}
		}
	}

	@Override
	/* * Handle the key pressed event from the text field. */
	public void keyPressed(KeyEvent e) {
		if (e.getKeyCode() == 112) { // F1
			moveCurrentOcatveDown();
		} else if (e.getKeyCode() == 113) { // F2
			moveCurrentOctaveUp();
		} else if (e.getKeyCode() == 114) { // F3
			deleteData();
		} else if (e.getKeyCode() == 115) { // F4
			noteOff();
		}
	}

	public void deleteData() {

		int row = table.getSelectedRow();
		if (row > -1) {
				table.setValueAt("000", row, Pattern.TRACK_0_NOTE);
				table.setValueAt("000", row, Pattern.TRACK_0_VOLUME);
				pattern.getData()[row][Pattern.TRACK_0_NOTE] = "000";
				pattern.getData()[row][Pattern.TRACK_0_VOLUME] = "000";
		}
	}

	private void noteOff() {
		int column = table.getSelectedColumn();
		int row = table.getSelectedRow();
		if (column > -1 && row > -1) {
				table.setValueAt("999", row, Pattern.TRACK_0_NOTE);
				table.setValueAt("000", row, Pattern.TRACK_0_VOLUME);
				pattern.getData()[row][Pattern.TRACK_0_NOTE] = "999";
				pattern.getData()[row][Pattern.TRACK_0_VOLUME] = "000";
		}
	}

	private void moveCurrentOctaveUp() {
		if (currentOctave < 6) {
			++currentOctave;
		}
	}

	private void moveCurrentOcatveDown() {
		if (currentOctave > 0) {
			--currentOctave;
		}
	}

	@Override
	/* * Handle the key released event from the text field. */
	public void keyReleased(KeyEvent e) {
		// displayInfo(e, "KEY RELEASED: ");
	}

	protected void displayInfo(KeyEvent e, String s) {
		String keyString, modString, tmpString, actionString, locationString;

		// You should only rely on the key char if the event
		// is a key typed event.
		int id = e.getID();
		if (id == KeyEvent.KEY_TYPED) {
			char c = e.getKeyChar();
			keyString = "key character = '" + c + "'";

		} else {
			int keyCode = e.getKeyCode();
			keyString = "key code = " + keyCode + " ("
					+ KeyEvent.getKeyText(keyCode) + ")";
		}

		int modifiers = e.getModifiersEx();
		modString = "modifiers = " + modifiers;
		tmpString = KeyEvent.getModifiersExText(modifiers);
		if (tmpString.length() > 0) {
			modString += " (" + tmpString + ")";
		} else {
			modString += " (no modifiers)";
		}

		actionString = "action key? ";
		if (e.isActionKey()) {
			actionString += "YES";
		} else {
			actionString += "NO";
		}

		locationString = "key location: ";
		int location = e.getKeyLocation();
		if (location == KeyEvent.KEY_LOCATION_STANDARD) {
			locationString += "standard";
		} else if (location == KeyEvent.KEY_LOCATION_LEFT) {
			locationString += "left";
		} else if (location == KeyEvent.KEY_LOCATION_RIGHT) {
			locationString += "right";
		} else if (location == KeyEvent.KEY_LOCATION_NUMPAD) {
			locationString += "numpad";
		} else { // (location == KeyEvent.KEY_LOCATION_UNKNOWN)
			locationString += "unknown";
		}

		String newline = "\n";
		log(s + newline + "    " + keyString + newline + "    " + modString
				+ newline + "    " + actionString + newline + "    "
				+ locationString + newline);
	}

	public void log(String text) {
		this.console.setText(this.console.getText() + text + "\n");
	}

	public void startPatternPlay() {
		if (sequencerTimer == null) {
			sequencerTimer = new Timer(sequencerDelay, this);
			sequencerTimer.start();
		} else if (!sequencerTimer.isRunning()) {
			sequencerTimer.restart();
		}
		soundSystem.generateMidiData(pattern);
		soundSystem.setSequencerPosition(0);
		soundSystem.playSequence();
	}

	public void stopPatternPlay() {
		if (sequencerTimer != null) {
			sequencerTimer.stop();
		}
		soundSystem.stopSequence();
	}

	public void scrollToVisible(JTable table, int rowIndex, int vColIndex) {
		if (!(table.getParent() instanceof JViewport)) {
			return;
		}
		JViewport viewport = (JViewport) table.getParent();
		Rectangle rect = table.getCellRect(rowIndex, vColIndex, true);
		Point pt = viewport.getViewPosition();
		rect.setLocation(rect.x - pt.x, rect.y - pt.y);
		viewport.scrollRectToVisible(rect);
	}
	
	public void askMidiInPort() {
		Object[] possibilities = soundSystem.getMidiInputDevices();
		String s = (String) JOptionPane.showInputDialog(this,
				"Choose your Midi IN device. Please: Do not plugin / plugout midi devices while the program is started.",
				"Config Midi IN", JOptionPane.QUESTION_MESSAGE, null, possibilities,
				null);

		// If a string was returned, say so.
		if ((s != null) && (s.length() > 0)) {
			int t = 0;
			boolean swExit = false;
			while(t < possibilities.length && swExit == false){
				if( ((String)possibilities[t]).equals(s)){
					swExit = true;
				}else{
					t++;
				}
			}
			if(t < possibilities.length){
				soundSystem.enableMidiInput(t,this);
				log("Midi device connected.");
			}else{
				log("Midi device not found.");
			}
		} else {
			JOptionPane.showMessageDialog(this,
					"No Midi IN device selected.");
		}
	}
	
	@Override
	public void close() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void send(MidiMessage message, long arg1) {
		log("midi received: " + message.getStatus());
		log("data: " + toHex(message.getMessage()));
		if(message.getStatus() == 144){ //note
			if(message.getMessage()[2] > 0x00){ //filter note off
				editNoteInCurrentPattern(message.getMessage()[1]);
			}
		}
	}
	
	public String toHex(byte[] command) {
		String str = "";
		for (byte i : command) {
			String value = Integer.toHexString(i).toUpperCase();
			if (value.length() == 1) {
				value = "0" + value;
			}
			str += (value + " ");
		}
		return str;
	}

}
